Skip to content

Add a SwiftUI SwipeMenu component#165

Merged
yysskk merged 2 commits into
mainfrom
feature/swiftui-swipe-menu
Jul 11, 2026
Merged

Add a SwiftUI SwipeMenu component#165
yysskk merged 2 commits into
mainfrom
feature/swiftui-swipe-menu

Conversation

@yysskk

@yysskk yysskk commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

Adds a SwiftUI counterpart to SwipeMenuView: a SwipeMenu view (iOS 18+) that presents the same scrollable tab bar above a horizontally paging content area, driven by a selection binding instead of a data source.

  • SwipeMenu — pages come from titles and a page view builder. Setting the binding is the equivalent of jump(to:animated:), and the onWillChangeIndex/onDidChangeIndex closures mirror the SwipeMenuViewDelegate paging callbacks, firing exactly once per move (swipe, tab tap, or binding change).
  • SwipeMenuOptions — a SwiftUI-native mirror of SwipeMenuViewOptions (Color/Font/EdgeInsets). Safe-area and clipping knobs are intentionally omitted; SwiftUI containers own those (ignoresSafeArea, clipped).
  • Behavior parity — the selection indicator interpolates its position and width between adjacent tabs while swiping, tab titles crossfade between their normal and selected colors, selectedFont does not affect layout, and the .flexible bar auto-scrolls to keep the indicator in view (the counterpart of TabView.focus(on:)).
  • SwipeMenuGeometry — the interpolation, focus, and paging math extracted into a pure nonisolated helper so it can be unit tested directly.
  • Example app — a SwiftUI demo (SwiftUIMenuView + SwiftUIOptionsView) sharing the existing SwipeMenuSettings model, presented full screen from a new floating button on the UIKit demo.
  • Docs — a new SwipeMenu in SwiftUI DocC article, landing-page topics, a README quick-start section, and a CHANGELOG entry.

The package still targets iOS 16: the SwiftUI surface is gated with @available(iOS 18.0, *) because it builds on scrollTargetBehavior(.paging), onScrollGeometryChange, ScrollPosition, and Color.mix.

New tests: SwipeMenuGeometryTests (17) and SwipeMenuOptionsTests (5) in the package, SwipeMenuSettingsSwiftUITests (4) in the example. Verified in the simulator that the default flexible/underline layout matches the UIKit demo and that segmented/circle with a non-zero initial selection renders correctly.

Checklist

  • Unit tests cover the change (bug fixes include a regression test)
  • xcodebuild test -scheme SwipeMenuViewController passes locally
  • swift format lint --strict passes and the tree is formatter-clean
  • Public API changes update the DocC documentation
  • User-facing changes have a CHANGELOG entry under Unreleased

SwipeMenu (iOS 18+) presents the same scrollable tab bar above a
horizontally paging content area as SwipeMenuView, with a SwiftUI-native
API: the selected page is a Binding<Int>, pages come from a view
builder, and appearance is configured with the new SwipeMenuOptions.
The onWillChangeIndex/onDidChangeIndex closures mirror the delegate
paging callbacks and fire exactly once per move.

The selection indicator and tab title colors are interpolated from the
content's continuous scroll progress, so they track the finger during
swipes, and the flexible tab bar scrolls to keep the indicator in view.
The interpolation math lives in SwipeMenuGeometry, a pure nonisolated
helper covered by unit tests, alongside defaults tests for
SwipeMenuOptions.

The example app gains a SwiftUI demo (menu, options form, and settings
mapping, all sharing the existing SwipeMenuSettings model) reachable
from a new floating button, the DocC catalog gains a "SwipeMenu in
SwiftUI" article, and the READMEs and changelog document the SwiftUI
support.
Comment thread Sources/SwipeMenuViewController/SwiftUI/SwipeMenu.swift

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid, well-tested addition (SwipeMenuGeometry's pure math is thoroughly unit tested, and SwipeMenuOptions/SwipeMenuSettings+SwiftUI mapping is covered too). Overall design and SwiftUI usage look sound — no concurrency/Sendable issues, no force-unwraps, no obvious security concerns.

Left one inline comment on SwipeMenu.commitScrollEnd(): when a drag interrupts a programmatic (tap/binding-driven) scroll and settles on a different page than intended, the code fires an onDidChangeIndex call reporting arrival at the intended page before firing a second will/did pair for the actual landed page. This means a single physical scroll settle can produce two onDidChangeIndex calls, one for a page transition that never actually appeared on screen — contradicting the "fires exactly once per move" doc guarantee. Worth fixing (use the actual landed value) or at least confirming this is intentional before merging, since consumers may rely on these callbacks for accurate page-view tracking/analytics.

… scroll

commitScrollEnd() fired onDidChangeIndex with the intended selection and
then a second will/did pair for the page the drag really settled on, so
a single physical settle produced two didChange calls — the first for a
transition that never appeared on screen. The settle handler now commits
the landed page directly and fires one pair per move; when interrupted,
the did's destination can differ from the one its will announced, which
the doc comment now spells out.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified the fix in 2b94e89: commitScrollEnd() now commits and reports the page the scroll actually landed on (rather than the intended selection) when a drag interrupts a programmatic move, so a single settle produces exactly one onDidChangeIndex call as documented. Traced through the interrupted-scroll scenario and the onChange(of: selection) re-entrancy guard — both behave correctly. This resolves the only issue raised in the previous review; no new problems found.

@yysskk yysskk merged commit 5bb1592 into main Jul 11, 2026
5 checks passed
@yysskk yysskk deleted the feature/swiftui-swipe-menu branch July 11, 2026 12:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant